package com.wsz.user.utils;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.wsz.pojo.*;
import com.wsz.pojo.Record;
import com.wsz.pojo.dto.BotDto;
import com.wsz.user.consumer.WebSocketServer;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;

@Data
public class Game extends Thread {
    private final Integer row;
    private final Integer col;
    private final Integer inner_walls_count;

    private final int [][] g;

    private final static int[] dx = {-1, 0, 1, 0};
    private final static int[] dy = {0, 1, 0, -1};

    private final Player playerA,playerB;

    private Integer nextStepA = null;
    private Integer nextStepB = null;

    private String status = "playing";//playing ---> finished
    private String loser = "";//all A B

    private ReentrantLock lock = new ReentrantLock(); //锁

    public Player getPlayerA() {
        return playerA;
    }

    public Player getPlayerB() {
        return playerB;
    }

    public void setNextStepA(Integer nextStepA) {
        lock.lock();
        try{
            this.nextStepA = nextStepA;
        }finally {
            lock.unlock();
        }
    }

    public void setNextStepB(Integer nextStepB) {
        lock.lock();
        try{
            this.nextStepB = nextStepB;
        }finally {
            lock.unlock();
        }
    }

    public Game(Integer row, Integer col, Integer inner_walls_count, String idA, String idB, Bot bot_A ,Bot bot_B) {
        this.row = row;
        this.col = col;
        this.inner_walls_count = inner_walls_count;
        this.g = new int[row][col];

        String bot_A_id = "-1",bot_B_id = "-1";
        String bot_A_code = "",bot_B_code = "";
        if(bot_A != null){
            bot_A_id = bot_A.getId();
            bot_A_code = bot_A.getContent();
        }

        if(bot_B != null){
            bot_B_id = bot_B.getId();
            bot_B_code = bot_B.getContent();
        }

        playerA = new Player(idA,row-2,1,new ArrayList<>(),bot_A_id,bot_A_code);
        playerB = new Player(idB,1,col-2,new ArrayList<>(),bot_B_id,bot_B_code);
    }

    private void updateUserRanting(Player player,Integer ranting){
        UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("id",player.getId());
        UserInfo userInfo = new UserInfo();
        userInfo.setRanting(ranting);
        WebSocketServer.userInfoMapper.update(userInfo,updateWrapper);
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            if(nextStep()){
                judge();
                if(status.equals("playing")){
                    sendMove();
                }else{
                    savaRecord();
                    sendResult();
                    break;
                }
            }else{
                status = "finished";
                lock.lock();
                try{
                    if(nextStepA == null && nextStepB == null){
                        loser = "all";
                    }else if( nextStepA == null){
                        loser = "A";
                    }else{
                        loser = "B";
                    }
                }finally {
                    lock.unlock();
                }
                sendResult();
                break;
            }
        }

    }

    private void savaRecord(){

        Integer rantingA = WebSocketServer.userInfoMapper.selectById(playerA.getId()).getRanting();
        Integer rantingB = WebSocketServer.userInfoMapper.selectById(playerB.getId()).getRanting();

        if("A".equals(loser)){
            rantingA -= 2;
            rantingB += 5;
        }else if("B".equals(loser)){
            rantingA += 5;
            rantingB -= 2;
        }


        updateUserRanting(playerA,rantingA);
        updateUserRanting(playerB,rantingB);

        Record record = new Record();
        record.setAId(playerA.getId());
        record.setASx(playerA.getSx());
        record.setAXy(playerA.getSy());
        record.setBId(playerB.getId());
        record.setBSx(playerB.getSx());
        record.setBXy(playerB.getSy());
        record.setASteps(JSONObject.toJSONString(playerA.getSteps()));
        record.setBSteps(JSONObject.toJSONString(playerB.getSteps()));
        record.setMap(JSONObject.toJSONString(g));
        record.setLoser(loser);

        WebSocketServer.recordMapper.insert(record);
    }

    private void judge(){
        List<Cell> CellA = playerA.getCell();
        List<Cell> CellB = playerB.getCell();

        Boolean validA = check_valid(CellA,CellB);
        Boolean validB = check_valid(CellB,CellA);

        if(!validA || !validB){
            status = "finished";

            if(!validA && !validB) {
                loser = "all";
            }else if(!validA){
                loser = "A";
            }else {
                loser = "B";
            }
        }

    }

    private Boolean check_valid(List<Cell> cellsA,List<Cell> cellsB){
        int n = cellsA.size();
        Cell cell = cellsA.get(n - 1);

        if(g[cell.getX()][cell.getY()] == 1) return false;

        for (int i = 0; i < n - 1; i++) {
            if(cellsA.get(i).getX() == cell.getX() && cellsA.get(i).getY() == cell.getY())
                return false;
        }

        for (int i = 0; i < n - 1; i++) {
            if(cellsB.get(i).getX() == cell.getX() && cellsB.get(i).getY() == cell.getY())
                return false;
        }
        return true;
    }

    private void sendResult(){
        JSONObject resp = new JSONObject();
        resp.put("event","result");
        resp.put("loser",loser);
        sendAllResult(resp.toJSONString());
    }


    private void sendMove(){
        lock.lock();
        try {
            JSONObject resp = new JSONObject();
            resp.put("event","move");
            resp.put("a_direction",nextStepA);
            resp.put("b_direction",nextStepB);
            sendAllResult(resp.toJSONString());
            nextStepA = nextStepB = null;
        }finally {
            lock.unlock();
        }
    }

    private void sendAllResult(String message){
        if(WebSocketServer.users.get(playerA.getId()) != null){
            WebSocketServer.users.get(playerA.getId()).sendMessage(message);
        }
        if(WebSocketServer.users.get(playerB.getId()) != null){
            WebSocketServer.users.get(playerB.getId()).sendMessage(message);
        }

    }

    private void sendBotCode(Player player){
        if(player.getBot_id().equals("-1")){
            return;
        }else{
            Player me,you;
            if(playerA.getId().equals(player.getId())){
                me = playerA;
                you = playerB;
            }else{
                me = playerB;
                you = playerA;
            }

            BotDto botDto = new BotDto();
            botDto.setG(g);
            botDto.setBotCode(player.getBot_code());
            botDto.setUserId(player.getId());
            botDto.setX(me.getSx());
            botDto.setY(me.getSy());
            botDto.setStep1(me.getSteps());
            botDto.setSx(you.getSx());
            botDto.setSy(you.getSy());
            botDto.setStep2(you.getSteps());
            WebSocketServer.botFeginClient.addBot(botDto);
        }
    }

    private Boolean nextStep(){
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        sendBotCode(playerA);
        sendBotCode(playerB);

        for (int i = 0; i < 25; i++) {
            try {
                Thread.sleep(200);
                lock.lock();
                try{
                    if(nextStepA != null && nextStepB != null) {
                        playerA.getSteps().add(nextStepA);
                        playerB.getSteps().add(nextStepB);
                        return true;
                    }
                }finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return false;
    }



    //检查地图连通性
    Boolean check(int sx,int sy,int tx,int ty){
        if (sx == tx && sy == ty) return true;
        g[sx][sy] = 1;

        for (int i = 0; i < 4; i ++ ) {
            int x = sx + dx[i], y = sy + dy[i];
            if (x >= 0 && x < this.row && y >= 0 && y < this.col && g[x][y] == 0) {
                if (check(x, y, tx, ty)) {
                    g[sx][sy] = 0;
                    return true;
                }
            }
        }

        g[sx][sy] = 0;
        return false;

    }


    public Boolean drawMap(){
        for (int i = 0; i < this.row; i++) {
            for (int j = 0; j < this.col; j++) {
                g[i][j] = 0;
            }
        }
        for(int r = 0; r < this.row; r++) {
            g[r][0] = g[r][this.col - 1] = 1;
        }

        for(int c = 0; c < this.col; c++){
            g[0][c] = g[this.row - 1][c] = 1;
        }

        Random random = new Random();

        for (int i = 0; i < this.inner_walls_count / 2; i ++ ) {
            for (int j = 0; j < 1000; j ++ ) {
                int r = random.nextInt(this.row);//0 ~ row -1
                int c = random.nextInt(this.col);
                if (g[r][c] == 1 || g[this.row - 1 - r][this.col - 1 - c] == 1) continue;
                if (r == this.row - 2 && c == 1 || r == 1 && c == this.col - 2)
                    continue;

                g[r][c] = g[this.row - 1 - r][this.col - 1 - c] = 1;
                break;
            }
        }


        return check(this.row-2 ,1 , 1 , this.col - 2);
    }

    public void createMap(){
        for (int i = 0; i < 1000; i++) {
            if(drawMap()) break;
        }
    }


}
